/*
 * Kaleidoscope application
 * Display an image as a kaleidoscope
 *
 * Luke Mitchell 03-04-12
 */

/*
 * TODO
 * 
 * n (where n is 2^x) segments
 * - reflection through line
 * Save image
 * Open image
 * Drag and pan
 * Pick size
 * Rotate
 *
 */

import javax.swing.*;
import javax.imageio.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.io.File;
import java.lang.Double;
import java.lang.Boolean;

public class Kaleidoscope implements Runnable
{
    private BufferedImage picture;
    private ImagePanel frame;

    // deal with an error
    private void error (String msg)
    {
        System.err.println ("Error: " + msg);
    }

    // returns true if the value specified is a power of 2
    private Boolean power_of_2 (int val)
    {
        if (val == 0)
            return false;

        // bitwise hack for checking power of 2
        // if the value is a power of 2 it will have
        // exactly one binary 1; the value of val - 1 will
        // then be a 0 in the 1 position, with all 1s in the
        // less significant bit positions - thus ANDing will be 0.
        return (val & (val - 1)) == 0;
    }

    // returns the coordinates (as a Point) of the centre of the image
    private Point get_centre (BufferedImage img)
    {
        return new Point (img.getWidth () / 2, img.getHeight () / 2);
    }

    // retrieves an image from a file (specified by name)
    private BufferedImage get_image (String filename)
    {
        BufferedImage ret = null;

        try
        {
            ret = ImageIO.read ( new File (filename));
        }

        catch (Exception e)
        {
            error ("Could not read image \"" + filename +  "\" (" + e + ")");
        }

        return ret;
    }

    // reverses an array
    private int[] reverse (int[] arr)
    {
        int[] ret = new int[arr.length];

        for (int i = 0; i < arr.length; i++)
            ret [i] = arr[arr.length - 1 - i];

        return ret;
    }

    // reflects the image in the y direction
    private BufferedImage reflect_vertical (BufferedImage img)
    {
        BufferedImage reflected = new BufferedImage (img.getWidth (), img.getHeight (), BufferedImage.TYPE_INT_ARGB);
        int[] row = new int[img.getWidth ()];
        
        for (int y = 0; y < img.getHeight (); y++)
        {
            row = img.getRGB (0, y, img.getWidth (), 1, row, 0, 1);
            reflected.setRGB (0, y, img.getWidth (), 1, reverse (row), 0, 1);
        }

        return reflected;
    }

    // reflects the image in the x direction
    private BufferedImage reflect_horizontal (BufferedImage img)
    {
        BufferedImage reflected = new BufferedImage (img.getWidth (), img.getHeight (), BufferedImage.TYPE_INT_ARGB);
        int height = img.getHeight ();

        for (int y = 0; y < height; y++)
            for (int x = 0; x < img.getWidth (); x++)
                reflected.setRGB (x, height - 1 - y, img.getRGB (x, y));

        return reflected;
    }

    // reflects image both in x and y
    private BufferedImage reflect_both (BufferedImage img)
    {
        return reflect_vertical (reflect_horizontal (img));
    }

    // create a kaleidoscope image with n segments
    // n should be a power of 2
    private BufferedImage kaleidoscope_ns (BufferedImage img, int n, Point centre)
    {
        BufferedImage assembled = new BufferedImage (img.getWidth (), img.getHeight (), BufferedImage.TYPE_INT_ARGB);
        BufferedImage tl, tr, bl, br;

        int maxy = (int) (centre.getY ());
        int maxx = (int) (centre.getX ());

        int subsegs = (n / 4);

        // check that n is a power of 2
        if (!power_of_2 (n))
        {
            error ("Number of segments must be a power of 2");
            return img;
        }

        // just one segment?
        if (n == 1)
            return img;

        // just two segments?
        if (n == 2)
        {
            // TODO
        }

        // prepare TL corner segment
        // this will be reflected to create the others
        for (int i = 0; i < subsegs; i++)
	{
            tl = new BufferedImage (maxx, maxy, BufferedImage.TYPE_INT_ARGB);

	    // 4 is the only power of 2 with a 'special case'
            // that is, the TL segment is copied as-is
            if (subsegs == 1)
            {
                int[] row = new int[maxx];

                for (int y = 0; y < maxy; y++)
                {
                    row = img.getRGB (0, y, maxx, 1, row, 0, 1);
                    tl.setRGB (0, y, maxx, 1, row, 0, 1);
                }
            }
            else
            {
                // no special cases here, split the segment into triangles
                int axis_subsegs = (subsegs / 2);
                int w = (maxx / axis_subsegs), h = (maxy / axis_subsegs);

                // TODO - reflection
                for (int j = 0; j < axis_subsegs; j++)
                {
                    Triangle t1 = new Triangle (new Point ((j * w), 0), new Point ((j * w) + w, 0), centre);
                    Point[] p1 = t1.contained();
                   
                    // TODO - this should be reflected through the line (j * w, 0) centre when j > 0
                    for (int k = 0; k < p1.length; k++)
                        tl.setRGB (Common.dtoi (p1[k].getX ()), Common.dtoi (p1[k].getY ()),
                            img.getRGB (Common.dtoi(p1[k].getX ()), Common.dtoi (p1[k].getY ())));
                    
                    Triangle t2 = new Triangle (new Point (0, (j * h)), new Point (0, (j * h) + h), centre);
                    Point[] p2 = t2.contained();
                    
                    // TODO - this should be reflected through the line (0,j*h) centre for all j
                    for (int k = 0; k < p2.length; k++)
                        tl.setRGB (Common.dtoi (p2[k].getX ()), Common.dtoi (p2[k].getY ()),
                            img.getRGB (Common.dtoi (p2[k].getX ()), Common.dtoi (p2[k].getY ())));
                }
            }
        }

        // the TR segment is reflected vertically
        tr = reflect_vertical (tl);

        // the BL segment is reflected horizontally
        bl = reflect_horizontal (tl);

        // the BR segment is reflected in both directions
        br = reflect_both (tl);

        // compose the final image
        // N.B. maxx and maxy are also the segment dimensions
        // used here somewhat confusingly
        for (int y = 0; y < img.getHeight (); y++)
        {
            int[] row = new int[maxx];

            for (int x = 0; x < img.getWidth (); x += maxx)
            {
                // TL
                if (y < maxy && x < maxx)
                {
                    row = tl.getRGB (0, y, maxx, 1, row, 0, 1);
                    assembled.setRGB (0, y, maxx, 1, row, 0, 1);
                    continue;
                }

                // TR
                if (y < maxy && x >= maxx)
                {
                    row = tr.getRGB (0, y, maxx, 1, row, 0, 1);
                    assembled.setRGB (maxx, y, maxx, 1, row, 0, 1);
                    continue;
                }

                // BL
                if (y >= maxy && x < maxx)
                {
                    if ((y - maxy) < maxy)
                    {
                        row = bl.getRGB (0, (y - maxy), maxx, 1, row, 0, 1);
                        assembled.setRGB (0, y, maxx, 1, row, 0, 1);
                    }
                    
                    continue;
                }

                // BR
                if (y >= maxy && x >= maxx)
                {
                    if ((y - maxy) < maxy)
                    {
                        row = br.getRGB (0, (y - maxy), maxx, 1, row, 0, 1);
                        assembled.setRGB (maxx, y, (maxx - 1), 1, row, 0, 1);
                    }
                    continue;
                }
            }
        }

        return assembled;
    }

    // set up the screen
    public void init ()
    {
        JFrame w = new JFrame ();
        w.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        w.setTitle ("Kaleidoscope");
        w.setLayout (new BorderLayout ());

        // TODO - load image

        picture = get_image ("plant4.jpg");
        picture = kaleidoscope_ns (picture, 8, get_centre (picture));
        
        if (picture == null)
        {
            error ("Null image returned!");
        }
        else
        {
            frame = new ImagePanel (picture);
            w.add (frame);
        }

        // TODO - proper kaleidoscope (8 segs, maybe 16)

        w.pack ();
        w.validate ();
        w.setLocationByPlatform (true);
        w.setVisible (true);
    }

    // run the application!
    public void run ()
    {
        init ();
    }

    public static void main (String[] args)
    {
        Kaleidoscope k = new Kaleidoscope ();
        SwingUtilities.invokeLater (k);
    }
}
